// 版权归Epic Games, Inc.所有。保留所有权利。

#include "SActorCanvas.h"

#include "Engine/GameViewportClient.h"
#include "IActorIndicatorWidget.h"
#include "Layout/ArrangedChildren.h"
#include "LyraIndicatorManagerComponent.h"
#include "SceneView.h"
#include "UI/IndicatorSystem/IndicatorDescriptor.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SLeafWidget.h"

class FSlateRect;

// 箭头方向命名空间
namespace EArrowDirection
{
	enum Type
	{
		Left, // 左
		Top, // 上
		Right, // 右
		Bottom, // 下
		MAX // 最大值
	};
}

// 箭头显示方向的旋转角度
const float ArrowRotations[EArrowDirection::MAX] =
{
	270.0f, // 左
	0.0f,   // 上
	90.0f,  // 右
	180.0f  // 下
};

// 每个箭头指向方向的偏移
const FVector2D ArrowOffsets[EArrowDirection::MAX] =
{
	FVector2D(-1.0f, 0.0f), // 左
	FVector2D(0.0f, -1.0f), // 上
	FVector2D(1.0f, 0.0f),  // 右
	FVector2D(0.0f, 1.0f)   // 下
};

// 角色画布箭头小部件类
class SActorCanvasArrowWidget : public SLeafWidget
{
public:

	SLATE_BEGIN_ARGS(SActorCanvasArrowWidget)
	{}
	/** 总是放在最后 */
	SLATE_END_ARGS()

	/** 构造函数 */
	SActorCanvasArrowWidget()
	: Rotation(0.0f) // 旋转角度
	, Arrow(nullptr) // 箭头画刷
	{

	}

	/** 每个小部件都需要一个这个 */
	/**
	 * 构造函数
	 * @param InArgs 参数
	 * @param ActorCanvasArrowBrush 角色画布箭头画刷
	 */
	void Construct(const FArguments& InArgs, const FSlateBrush* ActorCanvasArrowBrush)
	{
		Arrow = ActorCanvasArrowBrush; // 设置箭头画刷
		SetCanTick(false); // 设置不能tick
	}

	/**
	 * 绘制函数
	 * @param Args 绘制参数
	 * @param AllottedGeometry 分配的几何体
	 * @param MyClippingRect 我的裁剪矩形
	 * @param OutDrawElements 输出绘制元素列表
	 * @param LayerId 图层ID
	 * @param InWidgetStyle 小部件样式
	 * @param bParentEnabled 父级是否启用
	 * @return 最大图层ID
	 */
	virtual int32 OnPaint(const FPaintArgs& Args,
		const FGeometry& AllottedGeometry,
		const FSlateRect& MyClippingRect,
		FSlateWindowElementList& OutDrawElements,
		int32 LayerId,
		const FWidgetStyle& InWidgetStyle,
		bool bParentEnabled) const override
	{
		int32 MaxLayerId = LayerId; // 最大图层ID

		// 如果箭头画刷存在
		if (Arrow)
		{
			const bool bIsEnabled = ShouldBeEnabled(bParentEnabled); // 是否启用
			const ESlateDrawEffect DrawEffects = bIsEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; // 绘制效果
			const FColor FinalColorAndOpacity = (InWidgetStyle.GetColorAndOpacityTint() * Arrow->GetTint(InWidgetStyle)).ToFColor(true); // 最终颜色和不透明度

			// 创建旋转框绘制元素
			FSlateDrawElement::MakeRotatedBox(
				OutDrawElements,
				MaxLayerId++, // 图层ID递增
				AllottedGeometry.ToPaintGeometry(Arrow->ImageSize, FSlateLayoutTransform()), // 绘制几何体
				Arrow, // 画刷
				DrawEffects, // 绘制效果
				FMath::DegreesToRadians(GetRotation()), // 旋转角度（弧度）
				TOptional<FVector2D>(), // 可选中心点
				FSlateDrawElement::RelativeToElement, // 相对于元素
				FinalColorAndOpacity // 最终颜色
			);
		}

		return MaxLayerId; // 返回最大图层ID
	}

	/**
	 * 设置旋转角度
	 * @param InRotation 旋转角度
	 */
	FORCEINLINE void SetRotation(float InRotation)
	{
		Rotation = FMath::Fmod(InRotation, 360.0f); // 设置旋转角度，取模360
	}

	/**
	 * 获取旋转角度
	 * @return 旋转角度
	 */
	FORCEINLINE float GetRotation() const
	{
		return Rotation; // 返回旋转角度
	}

	/**
	 * 计算期望尺寸
	 * @param 缩放
	 * @return 期望尺寸
	 */
	virtual FVector2D ComputeDesiredSize(float) const override
	{
		// 如果箭头画刷存在，返回图像尺寸，否则返回零向量
		if (Arrow)
		{
			return Arrow->ImageSize; // 返回箭头图像尺寸
		}
		else
		{
			return FVector2D::ZeroVector; // 返回零向量
		}
	}

private:
	float Rotation; // 旋转角度
	
	const FSlateBrush* Arrow; // 箭头画刷
};

/**
 * 构造函数
 * @param InArgs 参数
 * @param InLocalPlayerContext 本地玩家上下文
 * @param InActorCanvasArrowBrush 角色画布箭头画刷
 */
void SActorCanvas::Construct(const FArguments& InArgs, const FLocalPlayerContext& InLocalPlayerContext, const FSlateBrush* InActorCanvasArrowBrush)
{
	LocalPlayerContext = InLocalPlayerContext; // 设置本地玩家上下文
	ActorCanvasArrowBrush = InActorCanvasArrowBrush; // 设置角色画布箭头画刷

	IndicatorPool.SetWorld(LocalPlayerContext.GetWorld()); // 设置指示器池的世界

	SetCanTick(false); // 设置不能tick
	SetVisibility(EVisibility::SelfHitTestInvisible); // 设置可见性为自身命中测试不可见

	// 创建10个箭头作为起始
	for (int32 i = 0; i < 10; ++i)
	{
		TSharedRef<SActorCanvasArrowWidget> ArrowWidget = SNew(SActorCanvasArrowWidget, ActorCanvasArrowBrush); // 创建箭头小部件
		ArrowWidget->SetVisibility(EVisibility::Collapsed); // 设置箭头小部件为折叠
		
		// 添加箭头插槽
		ArrowChildren.AddSlot(MoveTemp(
			FArrowSlot::FSlotArguments(MakeUnique<FArrowSlot>()) // 箭头插槽参数
			[
				ArrowWidget // 箭头小部件
			]
		));
	}

	UpdateActiveTimer(); // 更新活动计时器
}

/**
 * 更新画布
 * @param InCurrentTime 当前时间
 * @param InDeltaTime 增量时间
 * @return 活动计时器返回类型
 */
EActiveTimerReturnType SActorCanvas::UpdateCanvas(double InCurrentTime, float InDeltaTime)
{
	QUICK_SCOPE_CYCLE_COUNTER(STAT_SActorCanvas_UpdateCanvas); // 快速作用域周期计数器

	// 如果可选绘制几何体未设置，返回继续
	if (!OptionalPaintGeometry.IsSet())
	{
		return EActiveTimerReturnType::Continue; // 返回继续
	}

	// 获取本地玩家
	ULocalPlayer* LocalPlayer = LocalPlayerContext.GetLocalPlayer(); // 本地玩家
	ULyraIndicatorManagerComponent* IndicatorComponent = IndicatorComponentPtr.Get(); // 指示器组件
	// 如果指示器组件为空
	if (IndicatorComponent == nullptr)
	{
		// 从玩家控制器获取指示器组件
		IndicatorComponent = ULyraIndicatorManagerComponent::GetComponent(LocalPlayerContext.GetPlayerController());
		// 如果指示器组件存在
		if (IndicatorComponent)
		{
			// 世界可能已更改
			IndicatorPool.SetWorld(LocalPlayerContext.GetWorld()); // 设置指示器池的世界

			IndicatorComponentPtr = IndicatorComponent; // 设置指示器组件指针
			// 绑定指示器添加和移除事件
			IndicatorComponent->OnIndicatorAdded.AddSP(this, &SActorCanvas::OnIndicatorAdded);
			IndicatorComponent->OnIndicatorRemoved.AddSP(this, &SActorCanvas::OnIndicatorRemoved);
			// 为所有现有指示器调用添加回调
			for (UIndicatorDescriptor* Indicator : IndicatorComponent->GetIndicators())
			{
				OnIndicatorAdded(Indicator); // 调用指示器添加回调
			}
		}
		else
		{
			//TODO 隐藏所有内容
			return EActiveTimerReturnType::Continue; // 返回继续
		}
	}

	// 确保我们有一个玩家。如果没有，我们无法投影任何东西
	if (LocalPlayer)
	{
		const FGeometry PaintGeometry = OptionalPaintGeometry.GetValue(); // 获取绘制几何体

		FSceneViewProjectionData ProjectionData; // 场景视图投影数据
		// 如果获取投影数据成功
		if (LocalPlayer->GetProjectionData(LocalPlayer->ViewportClient->Viewport, /*out*/ ProjectionData))
		{
			SetShowAnyIndicators(true); // 设置显示任何指示器

			bool IndicatorsChanged = false; // 指示器是否改变

			// 遍历所有画布子项
			for (int32 ChildIndex = 0; ChildIndex < CanvasChildren.Num(); ++ChildIndex)
			{
				SActorCanvas::FSlot& CurChild = CanvasChildren[ChildIndex]; // 当前子项
				UIndicatorDescriptor* Indicator = CurChild.Indicator; // 指示器描述符

				// 如果插槽内容无效且我们有权限移除它
				if (Indicator->CanAutomaticallyRemove())
				{
					IndicatorsChanged = true; // 标记指示器改变

					RemoveIndicatorForEntry(Indicator); // 移除条目指示器
					// 递减当前索引以考虑移除
					--ChildIndex;
					continue; // 继续下一个
				}

				CurChild.SetIsIndicatorVisible(Indicator->GetIsVisible()); // 设置指示器是否可见

				// 如果指示器不可见，继续下一个
				if (!CurChild.GetIsIndicatorVisible())
				{
					IndicatorsChanged |= CurChild.bIsDirty(); // 如果脏则标记改变
					CurChild.ClearDirtyFlag(); // 清除脏标志
					continue; // 继续下一个
				}

				// 如果指示器在更新之间改变了限制状态，通知指示器并标记指示器改变
				if (CurChild.WasIndicatorClampedStatusChanged())
				{
					//Indicator->OnIndicatorClampedStatusChanged(CurChild.WasIndicatorClamped());
					CurChild.ClearIndicatorClampedStatusChangedFlag(); // 清除限制状态改变标志
					IndicatorsChanged = true; // 标记指示器改变
				}

				FVector ScreenPositionWithDepth; // 带深度的屏幕位置

				FIndicatorProjection Projector; // 指示器投影器
				// 尝试投影
				const bool Success = Projector.Project(*Indicator, ProjectionData, PaintGeometry.Size, OUT ScreenPositionWithDepth);

				// 如果投影失败
				if (!Success)
				{
					CurChild.SetHasValidScreenPosition(false); // 设置没有有效的屏幕位置
					CurChild.SetInFrontOfCamera(false); // 设置不在相机前方

					IndicatorsChanged |= CurChild.bIsDirty(); // 如果脏则标记改变
					CurChild.ClearDirtyFlag(); // 清除脏标志
					continue; // 继续下一个
				}

				CurChild.SetInFrontOfCamera(Success); // 设置是否在相机前方
				// 设置是否有有效的屏幕位置：在相机前方或指示器限制到屏幕
				CurChild.SetHasValidScreenPosition(CurChild.GetInFrontOfCamera() || Indicator->GetClampToScreen());

				// 如果有有效的屏幕位置
				if (CurChild.HasValidScreenPosition())
				{
					// 只有当我们能实际显示这个指示器时才脏屏幕位置
					CurChild.SetScreenPosition(FVector2D(ScreenPositionWithDepth)); // 设置屏幕位置
					CurChild.SetDepth(ScreenPositionWithDepth.X); // 设置深度
				}

				CurChild.SetPriority(Indicator->GetPriority()); // 设置优先级

				IndicatorsChanged |= CurChild.bIsDirty(); // 如果脏则标记改变
				CurChild.ClearDirtyFlag(); // 清除脏标志
			}

			// 如果指示器改变，无效化绘制
			if (IndicatorsChanged)
			{
				Invalidate(EInvalidateWidget::Paint); // 无效化小部件绘制
			}
		}
		else
		{
			SetShowAnyIndicators(false); // 设置不显示任何指示器
		}
	}
	else
	{
		SetShowAnyIndicators(false); // 设置不显示任何指示器
	}

	// 如果没有指示器，停止计时器，否则继续
	if (AllIndicators.Num() == 0)
	{
		TickHandle.Reset(); // 重置计时器句柄
		return EActiveTimerReturnType::Stop; // 返回停止
	}
	else
	{
		return EActiveTimerReturnType::Continue; // 返回继续
	}
}

/**
 * 设置是否显示任何指示器
 * @param bIndicators 是否显示指示器
 */
void SActorCanvas::SetShowAnyIndicators(bool bIndicators)
{
	// 如果显示状态改变
	if (bShowAnyIndicators != bIndicators)
	{
		bShowAnyIndicators = bIndicators; // 设置显示任何指示器

		// 如果不显示任何指示器，将所有子项设置为折叠
		if (!bShowAnyIndicators)
		{
			for (int32 ChildIndex = 0; ChildIndex < AllChildren.Num(); ChildIndex++)
			{
				AllChildren.GetChildAt(ChildIndex)->SetVisibility(EVisibility::Collapsed); // 设置子项为折叠
			}
		}
	}
}

/**
 * 排列子项
 * @param AllottedGeometry 分配的几何体
 * @param ArrangedChildren 排列的子项
 */
void SActorCanvas::OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const
{
	QUICK_SCOPE_CYCLE_COUNTER(STAT_SActorCanvas_OnArrangeChildren); // 快速作用域周期计数器

	NextArrowIndex = 0; // 重置下一个箭头索引

	// 确保我们有一个玩家。如果没有，我们无法投影任何东西
	if (bShowAnyIndicators)
	{
		const FVector2D ArrowWidgetSize = ActorCanvasArrowBrush->GetImageSize(); // 箭头小部件尺寸
		const FIntPoint FixedPadding = FIntPoint(10.0f, 10.0f) + FIntPoint(ArrowWidgetSize.X, ArrowWidgetSize.Y); // 固定内边距
		const FVector Center = FVector(AllottedGeometry.Size * 0.5f, 0.0f); // 中心点

		// 排序子项
		TArray<const SActorCanvas::FSlot*> SortedSlots; // 排序后的插槽数组
		for (int32 ChildIndex = 0; ChildIndex < CanvasChildren.Num(); ++ChildIndex)
		{
			SortedSlots.Add(&CanvasChildren[ChildIndex]); // 添加插槽
		}

		// 稳定排序：先按优先级，再按深度
		SortedSlots.StableSort([](const SActorCanvas::FSlot& A, const SActorCanvas::FSlot& B)
		{
			return A.GetPriority() == B.GetPriority() ? A.GetDepth() > B.GetDepth() : A.GetPriority() < B.GetPriority();
		});

		// 遍历所有排序后的子项
		for (int32 ChildIndex = 0; ChildIndex < SortedSlots.Num(); ++ChildIndex)
		{
			// 获取一个子项
			const SActorCanvas::FSlot& CurChild = *SortedSlots[ChildIndex]; // 当前子项
			const UIndicatorDescriptor* Indicator = CurChild.Indicator; // 指示器描述符

			// 如果这个指示器无效或有无效的世界位置，跳过
			if (!ArrangedChildren.Accepts(CurChild.GetWidget()->GetVisibility()))
			{
				CurChild.SetWasIndicatorClamped(false); // 设置指示器未被限制
				continue; // 继续下一个
			}

			FVector2D ScreenPosition = CurChild.GetScreenPosition(); // 屏幕位置
			const bool bInFrontOfCamera = CurChild.GetInFrontOfCamera(); // 是否在相机前方

			// 如果我们无法投影位置且指示器不想被限制，则跳过
			const bool bShouldClamp = Indicator->GetClampToScreen(); // 是否应该限制

			// 获取插槽的偏移和最终尺寸
			FVector2D SlotSize, SlotOffset, SlotPaddingMin, SlotPaddingMax; // 插槽尺寸、偏移、最小内边距、最大内边距
			GetOffsetAndSize(Indicator, SlotSize, SlotOffset, SlotPaddingMin, SlotPaddingMax); // 获取偏移和尺寸

			bool bWasIndicatorClamped = false; // 指示器是否被限制

			// 如果我们不需要限制这个东西，可以跳过很多工作
			if (bShouldClamp)
			{
				// 判断我们是否限制到了屏幕的任何边缘
				EArrowDirection::Type ClampDir = EArrowDirection::MAX; // 限制方向

				// 确定要限制在内的内部屏幕矩形尺寸
				const FIntPoint RectMin = FIntPoint(SlotPaddingMin.X, SlotPaddingMin.Y) + FixedPadding; // 矩形最小点
				const FIntPoint RectMax = FIntPoint(AllottedGeometry.Size.X - SlotPaddingMax.X, AllottedGeometry.Size.Y - SlotPaddingMax.Y) - FixedPadding; // 矩形最大点
				const FIntRect ClampRect(RectMin, RectMax); // 限制矩形

				// 确保屏幕位置在限制矩形内
				if (!ClampRect.Contains(FIntPoint(ScreenPosition.X, ScreenPosition.Y)))
				{
					// 定义四个平面（左、上、右、下）
					const FPlane Planes[] =
					{
						FPlane(FVector(1.0f, 0.0f, 0.0f), ClampRect.Min.X),	// 左
						FPlane(FVector(0.0f, 1.0f, 0.0f), ClampRect.Min.Y),	// 上
						FPlane(FVector(-1.0f, 0.0f, 0.0f), -ClampRect.Max.X),	// 右
						FPlane(FVector(0.0f, -1.0f, 0.0f), -ClampRect.Max.Y)	// 下
					};

					// 检查与每个平面的交点
					for (int32 i = 0; i < EArrowDirection::MAX; ++i)
					{
						FVector NewPoint; // 新点
						// 如果线段与平面相交
						if (FMath::SegmentPlaneIntersection(Center, FVector(ScreenPosition, 0.0f), Planes[i], NewPoint))
						{
							ClampDir = (EArrowDirection::Type)i; // 设置限制方向
							ScreenPosition = FVector2D(NewPoint); // 设置屏幕位置
						}
					}
				}
				else if (!bInFrontOfCamera) // 如果不在相机前方但在屏幕内
				{
					const float ScreenXNorm = ScreenPosition.X / (RectMax.X - RectMin.X); // 屏幕X标准化
					const float ScreenYNorm = ScreenPosition.Y / (RectMax.Y - RectMin.Y); // 屏幕Y标准化
					// 我们需要把这个东西固定到屏幕的一侧
					if (ScreenXNorm < ScreenYNorm)
					{
						if (ScreenXNorm < (-ScreenYNorm + 1.0f))
						{
							ClampDir = EArrowDirection::Left; // 限制方向为左
							ScreenPosition.X = ClampRect.Min.X; // 设置X坐标为矩形最小X
						}
						else
						{
							ClampDir = EArrowDirection::Bottom; // 限制方向为下
							ScreenPosition.Y = ClampRect.Max.Y; // 设置Y坐标为矩形最大Y
						}
					}
					else
					{
						if (ScreenXNorm < (-ScreenYNorm + 1.0f))
						{
							ClampDir = EArrowDirection::Top; // 限制方向为上
							ScreenPosition.Y = ClampRect.Min.Y; // 设置Y坐标为矩形最小Y
						}
						else
						{
							ClampDir = EArrowDirection::Right; // 限制方向为右
							ScreenPosition.X = ClampRect.Max.X; // 设置X坐标为矩形最大X
						}
					}
				}

				bWasIndicatorClamped = (ClampDir != EArrowDirection::MAX); // 设置指示器是否被限制

				// 是否应该显示箭头
				if (Indicator->GetShowClampToScreenArrow() &&
					bWasIndicatorClamped &&
					ArrowChildren.IsValidIndex(NextArrowIndex)) // 如果应该显示箭头且被限制且有可用的箭头小部件
				{
					const FVector2D ArrowOffsetDirection = ArrowOffsets[ClampDir]; // 箭头偏移方向
					const float ArrowRotation = ArrowRotations[ClampDir]; // 箭头旋转角度

					// 获取一个箭头小部件
					TSharedRef<SActorCanvasArrowWidget> ArrowWidgetToUse = StaticCastSharedRef<SActorCanvasArrowWidget>(ArrowChildren.GetChildAt(NextArrowIndex)); // 箭头小部件
					NextArrowIndex++; // 递增下一个箭头索引

					// 设置箭头的旋转
					ArrowWidgetToUse->SetRotation(ArrowRotation); // 设置旋转角度

					// 计算偏移的大小
					const FVector2D OffsetMagnitude = (SlotSize + ArrowWidgetSize) * 0.5f; // 偏移大小

					// 用于将箭头居中在位置上
					const FVector2D ArrowCenteringOffset = -(ArrowWidgetSize * 0.5f); // 箭头居中偏移

					FVector2D ArrowAlignmentOffset = FVector2D::ZeroVector; // 箭头对齐偏移
					// 根据垂直对齐方式设置对齐偏移
					switch (Indicator->VAlignment)
					{
					case VAlign_Top: // 顶部对齐
						ArrowAlignmentOffset = SlotSize * FVector2D(0.0f, 0.5f); // 设置对齐偏移
						break;
					case VAlign_Bottom: // 底部对齐
						ArrowAlignmentOffset = SlotSize * FVector2D(0.0f, -0.5f); // 设置对齐偏移
						break;
					}

					// 计算箭头的偏移
					const FVector2D WidgetOffset = (OffsetMagnitude * ArrowOffsetDirection); // 小部件偏移

					const FVector2D FinalOffset = (WidgetOffset + ArrowAlignmentOffset + ArrowCenteringOffset); // 最终偏移

					// 获取最终位置
					const FVector2D FinalPosition = (ScreenPosition + FinalOffset); // 最终位置

					ArrowWidgetToUse->SetVisibility(EVisibility::HitTestInvisible); // 设置箭头小部件为命中测试不可见

					// 在指示器上方注入箭头
					ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(
						ArrowWidgetToUse,			// 正在排列的子小部件
						FinalPosition,				// 子项的本地位置（即在父项内的位置）
						ArrowWidgetSize,			// 子项的尺寸
						1.f							// 子项的缩放
					));
				}
			}

			CurChild.SetWasIndicatorClamped(bWasIndicatorClamped); // 设置指示器是否被限制

			// 将这个子项的信息添加到输出列表（ArrangedChildren）
			ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(
				CurChild.GetWidget(), // 子小部件
				ScreenPosition + SlotOffset, // 位置
				SlotSize, // 尺寸
				1.f // 缩放
			));
		}
	}

	// 如果下一个箭头索引小于上次更新的箭头索引，隐藏多余的箭头
	if (NextArrowIndex < ArrowIndexLastUpdate)
	{
		for (int32 ArrowRemovedIndex = NextArrowIndex; ArrowRemovedIndex < ArrowIndexLastUpdate; ArrowRemovedIndex++)
		{
			ArrowChildren.GetChildAt(ArrowRemovedIndex)->SetVisibility(EVisibility::Collapsed); // 设置箭头小部件为折叠
		}
	}

	ArrowIndexLastUpdate = NextArrowIndex; // 更新上次更新的箭头索引
}

/**
 * 绘制函数
 * @param Args 绘制参数
 * @param AllottedGeometry 分配的几何体
 * @param MyCullingRect 我的剔除矩形
 * @param OutDrawElements 输出绘制元素列表
 * @param LayerId 图层ID
 * @param InWidgetStyle 小部件样式
 * @param bParentEnabled 父级是否启用
 * @return 最大图层ID
 */
int32 SActorCanvas::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
	QUICK_SCOPE_CYCLE_COUNTER(STAT_SActorCanvas_OnPaint); // 快速作用域周期计数器

	OptionalPaintGeometry = AllottedGeometry; // 设置可选绘制几何体

	FArrangedChildren ArrangedChildren(EVisibility::Visible); // 排列的子项
	ArrangeChildren(AllottedGeometry, ArrangedChildren); // 排列子项

	int32 MaxLayerId = LayerId; // 最大图层ID

	const FPaintArgs NewArgs = Args.WithNewParent(this); // 新的绘制参数
	const bool bShouldBeEnabled = ShouldBeEnabled(bParentEnabled); // 是否应该启用

	// 遍历所有排列的小部件
	for (const FArrangedWidget& CurWidget : ArrangedChildren.GetInternalArray())
	{
		// 如果子小部件没有被剔除
		if (!IsChildWidgetCulled(MyCullingRect, CurWidget))
		{
			SWidget* MutableWidget = const_cast<SWidget*>(&CurWidget.Widget.Get()); // 可变小部件

			// 绘制小部件
			const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, MyCullingRect, OutDrawElements, bDrawElementsInOrder ? MaxLayerId : LayerId, InWidgetStyle, bShouldBeEnabled);
			MaxLayerId = FMath::Max(MaxLayerId, CurWidgetsMaxLayerId); // 更新最大图层ID
		}
		else
		{
			//SlateGI - 移除内容
		}
	}

	return MaxLayerId; // 返回最大图层ID
}

/**
 * 获取引用者名称
 * @return 引用者名称
 */
FString SActorCanvas::GetReferencerName() const
{
	return TEXT("SActorCanvas"); // 返回字符串
}

/**
 * 添加引用对象
 * @param Collector 引用收集器
 */
void SActorCanvas::AddReferencedObjects( FReferenceCollector& Collector )
{
	Collector.AddReferencedObjects(AllIndicators); // 添加所有指示器到引用收集器
}

/**
 * 指示器添加回调
 * @param Indicator 指示器描述符
 */
void SActorCanvas::OnIndicatorAdded(UIndicatorDescriptor* Indicator)
{
	AllIndicators.Add(Indicator); // 添加到所有指示器
	InactiveIndicators.Add(Indicator); // 添加到非活动指示器
	
	AddIndicatorForEntry(Indicator); // 为条目添加指示器
}

/**
 * 指示器移除回调
 * @param Indicator 指示器描述符
 */
void SActorCanvas::OnIndicatorRemoved(UIndicatorDescriptor* Indicator)
{
	RemoveIndicatorForEntry(Indicator); // 为条目移除指示器
	
	AllIndicators.Remove(Indicator); // 从所有指示器移除
	InactiveIndicators.Remove(Indicator); // 从非活动指示器移除
}

/**
 * 为条目添加指示器
 * @param Indicator 指示器描述符
 */
void SActorCanvas::AddIndicatorForEntry(UIndicatorDescriptor* Indicator)
{
	// 异步加载指示器，并池化结果，以便轻松使用和重用小部件
	TSoftClassPtr<UUserWidget> IndicatorClass = Indicator->GetIndicatorClass(); // 指示器类
	if (!IndicatorClass.IsNull()) // 如果指示器类不为空
	{
		TWeakObjectPtr<UIndicatorDescriptor> IndicatorPtr(Indicator); // 指示器弱引用
		// 异步加载
		AsyncLoad(IndicatorClass, [this, IndicatorPtr, IndicatorClass]() {
			// 如果指示器仍然有效
			if (UIndicatorDescriptor* Indicator = IndicatorPtr.Get())
			{
				// 在异步加载这个指示器小部件期间，我们可能已经移除了它
				if (!AllIndicators.Contains(Indicator)) // 如果所有指示器不包含该指示器
				{
					return; // 返回
				}

				// 从池中创建小部件
				if (UUserWidget* IndicatorWidget = IndicatorPool.GetOrCreateInstance(TSubclassOf<UUserWidget>(IndicatorClass.Get()))) // 获取或创建实例
				{
					// 如果小部件类实现了IIndicatorWidgetInterface接口
					if (IndicatorWidget->GetClass()->ImplementsInterface(UIndicatorWidgetInterface::StaticClass()))
					{
						IIndicatorWidgetInterface::Execute_BindIndicator(IndicatorWidget, Indicator); // 执行绑定指示器
					}

					Indicator->IndicatorWidget = IndicatorWidget; // 设置指示器小部件

					InactiveIndicators.Remove(Indicator); // 从非活动指示器移除

					// 添加角色插槽
					AddActorSlot(Indicator)
					[
						SAssignNew(Indicator->CanvasHost, SBox) // 分配新的SBox作为画布宿主
						[
							IndicatorWidget->TakeWidget() // 获取小部件
						]
					];
				}
			}
		});
		StartAsyncLoading(); // 开始异步加载
	}
}

/**
 * 为条目移除指示器
 * @param Indicator 指示器描述符
 */
void SActorCanvas::RemoveIndicatorForEntry(UIndicatorDescriptor* Indicator)
{
	// 如果指示器小部件存在
	if (UUserWidget* IndicatorWidget = Indicator->IndicatorWidget.Get())
	{
		// 如果小部件类实现了IIndicatorWidgetInterface接口
		if (IndicatorWidget->GetClass()->ImplementsInterface(UIndicatorWidgetInterface::StaticClass()))
		{
			IIndicatorWidgetInterface::Execute_UnbindIndicator(IndicatorWidget, Indicator); // 执行解绑指示器
		}

		Indicator->IndicatorWidget = nullptr; // 重置指示器小部件
		
		IndicatorPool.Release(IndicatorWidget); // 释放指示器小部件
	}

	TSharedPtr<SWidget> CanvasHost = Indicator->CanvasHost.Pin(); // 获取画布宿主
	if (CanvasHost.IsValid()) // 如果画布宿主有效
	{
		RemoveActorSlot(CanvasHost.ToSharedRef()); // 移除角色插槽
		Indicator->CanvasHost.Reset(); // 重置画布宿主
	}
}

/**
 * 添加角色插槽
 * @param Indicator 指示器描述符
 * @return 作用域小部件插槽参数
 */
SActorCanvas::FScopedWidgetSlotArguments SActorCanvas::AddActorSlot(UIndicatorDescriptor* Indicator)
{
	TWeakPtr<SActorCanvas> WeakCanvas = SharedThis(this); // 弱画布引用
	// 返回作用域小部件插槽参数
	return FScopedWidgetSlotArguments{ MakeUnique<FSlot>(Indicator), this->CanvasChildren, INDEX_NONE
		, [WeakCanvas](const FSlot*, int32)
		{
			// 如果画布仍然有效，更新活动计时器
			if (TSharedPtr<SActorCanvas> Canvas = WeakCanvas.Pin())
			{
				Canvas->UpdateActiveTimer(); // 更新活动计时器
			}
		}};
}

/**
 * 移除角色插槽
 * @param SlotWidget 插槽小部件
 * @return 移除的索引
 */
int32 SActorCanvas::RemoveActorSlot(const TSharedRef<SWidget>& SlotWidget)
{
	// 遍历所有画布子项
	for (int32 SlotIdx = 0; SlotIdx < CanvasChildren.Num(); ++SlotIdx)
	{
		// 如果找到匹配的插槽小部件
		if ( SlotWidget == CanvasChildren[SlotIdx].GetWidget() )
		{
			CanvasChildren.RemoveAt(SlotIdx); // 移除插槽

			UpdateActiveTimer(); // 更新活动计时器

			return SlotIdx; // 返回移除的索引
		}
	}

	return -1; // 返回-1表示未找到
}

/**
 * 获取偏移和尺寸
 * @param Indicator 指示器描述符
 * @param OutSize 输出尺寸
 * @param OutOffset 输出偏移
 * @param OutPaddingMin 输出最小内边距
 * @param OutPaddingMax 输出最大内边距
 */
void SActorCanvas::GetOffsetAndSize(const UIndicatorDescriptor* Indicator,
	FVector2D& OutSize, 
	FVector2D& OutOffset,
	FVector2D& OutPaddingMin,
	FVector2D& OutPaddingMax) const
{
	// 这个可能有一天会被使用
	FVector2D AllottedSize = FVector2D::ZeroVector; // 分配的尺寸

	// 获取子小部件的期望尺寸
	TSharedPtr<SWidget> CanvasHost = Indicator->CanvasHost.Pin(); // 画布宿主
	if (CanvasHost.IsValid()) // 如果画布宿主有效
	{
		OutSize = CanvasHost->GetDesiredSize(); // 获取期望尺寸
	}

	// 处理水平对齐
	switch(Indicator->GetHAlign())
	{
		case HAlign_Left: // 与Align_Top相同
			OutOffset.X = 0.0f; // 偏移X为0
			OutPaddingMin.X = 0.0f; // 最小内边距X为0
			OutPaddingMax.X = OutSize.X; // 最大内边距X为尺寸X
			break;
		
		case HAlign_Center: // 居中对齐
			OutOffset.X = (AllottedSize.X - OutSize.X) / 2.0f; // 偏移X为居中
			OutPaddingMin.X = OutSize.X / 2.0f; // 最小内边距X为尺寸X的一半
			OutPaddingMax.X = OutPaddingMin.X; // 最大内边距X同最小
			break;
		
		case HAlign_Right: // 与Align_Bottom相同
			OutOffset.X = AllottedSize.X - OutSize.X; // 偏移X为右对齐
			OutPaddingMin.X = OutSize.X; // 最小内边距X为尺寸X
			OutPaddingMax.X = 0.0f; // 最大内边距X为0
			break;
	}

	// 现在，处理垂直对齐
	switch(Indicator->GetVAlign())
	{
		case VAlign_Top: // 顶部对齐
			OutOffset.Y = 0.0f; // 偏移Y为0
			OutPaddingMin.Y = 0.0f; // 最小内边距Y为0
			OutPaddingMax.Y = OutSize.Y; // 最大内边距Y为尺寸Y
			break;
		
		case VAlign_Center: // 居中对齐
			OutOffset.Y = (AllottedSize.Y - OutSize.Y) / 2.0f; // 偏移Y为居中
			OutPaddingMin.Y = OutSize.Y / 2.0f; // 最小内边距Y为尺寸Y的一半
			OutPaddingMax.Y = OutPaddingMin.Y; // 最大内边距Y同最小
			break;
		
		case VAlign_Bottom: // 底部对齐
			OutOffset.Y = AllottedSize.Y - OutSize.Y; // 偏移Y为底部对齐
			OutPaddingMin.Y = OutSize.Y; // 最小内边距Y为尺寸Y
			OutPaddingMax.Y = 0.0f; // 最大内边距Y为0
			break;
	}
}

/**
 * 更新活动计时器
 */
void SActorCanvas::UpdateActiveTimer()
{
	// 需要tick的条件：有指示器或指示器组件无效
	const bool NeedsTicks = AllIndicators.Num() > 0 || !IndicatorComponentPtr.IsValid();

	// 如果需要tick且计时器句柄无效，注册活动计时器
	if (NeedsTicks && !TickHandle.IsValid())
	{
		TickHandle = RegisterActiveTimer(0, FWidgetActiveTimerDelegate::CreateSP(this, &SActorCanvas::UpdateCanvas)); // 注册活动计时器
	}
}